package DBIx::Class::Component::DataValidator;

use strict;
use warnings;

use Class::C3;
use base qw/
    DBIx::Class
/;
use Clone qw/ clone /;
use Data::Dumper;
use Carp qw/ croak /;

our $VERSION = '0.05';


=head1 NAME

DBIx::Class::Component::DataValidator - Data validation for create and update

=head1 DESCRIPTION

Data validation directly in the Model. This model is for update methods only.
It also implements the whole validation logic..

See L<DBIx::Class::DataValidator> for more info..


=head1 ATTENTION

Dont forget, you have to do BOTH: use the DBIx::Class::ResultSet::DataValidator AND the DBIx::Class::DataValidator!! One of them wont work (actually the DBIx::Class::DataValidator alone may work but not a good idea)

=cut

use Hash::Merge qw/ merge /;
use Clone qw/ clone /;
use Scalar::Util qw/ blessed weaken /;

use Data::FormValidator;


=pod CLASSDATA

=pod _validation_profiles

=cut

=pod _validation_by_column

=cut

=pod _validation_methods

=cut

__PACKAGE__->mk_classdata( $_ ) for qw/
    disable_native_validation
    _disable_native_validation_temp
    _validation_profiles
    _validation_by_column
    _validation_methods
    _validation_column_class
    _unset_validation_datatype
    _validation_dbms_inited
    _deflate_before_validation
    _data_form_validator
    _disable_datatype_validation
    _runtime_validation_methods
/;

__PACKAGE__->_data_form_validator( Data::FormValidator->new );

__PACKAGE__->load_components( qw/ Component::DataValidator::DBMS / );


=head1 METHODS

=head2 add_validation_profiles

adds a new named validator to the model. This "named validator" can be re-used
at any time! Even using multiple validators at once is possible.

    __PACKAGE__->add_validation_profiles( {
        default => {
            required   => [ qw/ field1 field2 / ],
            constraint => {
                field1 => qr~^\d+$~,
                field2 => sub { my ( $dv, $v ) = @_; return $v =~ /^A/ || $v eq ''; }
            },
            filter     => [ qw/ trim / ]
        },
        create => { .. },
        update => { .. },
        other  => { .. }
    } );

And later:

    # calls "default"
    $obj->validates();
    
    # calls "default" and "create" and "other"
    $obj->validates( qw/ default create other / );
    
    # those will throw an Exception if neither "default" nor "create" validator exitsts!
    MySchema::ModelName->create_validated( $create_data_ref );
    MySchema::ModelName->create_validated( $create_data_ref, "create" );
    
    # this will throw an Exception if neither "default" nor "update" validator exitsts!
    MySchema::ModelName->update_validated( $create_data_ref );

=cut

sub add_validation_profiles {
    my ( $self, $validators_ref ) = @_;
    $self->_validation_profiles( {} ) unless $self->_validation_profiles;
    foreach my $k( keys %{ $validators_ref } ) {
        $self->_validation_profiles->{ $k } = $validators_ref->{ $k };
    }
    return;
}


=head2 add_columns

Parses the to be added columns for extra attributes and applies DBMS dependent data type validation

    __PACKAGE__->add_columns( 
        colname => {
            # .. some
            validation => {
                required   => 1,
                constraint => qr~^XYZ~, # begins with XYZ
            }
        },
        # .. and more
    );

=cut

my ( %seen, %overall );

sub add_columns {
    my ( $self, @columns ) = @_;
    
    # init _validation_* as hashref
    $self->_validation_by_column( {} )
        unless $self->_validation_by_column;
    $self->_validation_profiles( {} )
        unless $self->_validation_profiles;
    $self->_validation_column_class( {} )
        unless $self->_validation_column_class;
    $self->_unset_validation_datatype( {} )
        unless $self->_unset_validation_datatype;
    
    # parse by col
    my $c = $#columns;
    my @add_columns = ();
    for ( my $i = 0; $i <= $c; $i++ ) {
        
        # get the current col
        my $col = $columns[ $i ];
        push @add_columns, $col;
        
        if ( scalar @columns > $i+1 ) {
            my $ref;
            if( ! ref( $columns[ $i+1 ] ) && $columns[ $i+1 ] =~ /^class:(\w+?)$/ ) {
                my $validation_class = $1;
                die "Cant use validation class '$validation_class' for column '$col' in '$self'"
                    unless defined $self->_validation_column_class->{ $validation_class };
                $ref = $self->_validation_column_class->{ $validation_class };
            }
            
            # if next is ref .. it is 'col => {}' structure ...
            if( $ref || ref( $ref = $columns[ $i+1 ] ) ) {
                push @add_columns, $ref;
                
                # no specific validation  .. try to get some elsewhere
                unless ( defined $ref->{ validation } ) {
                    
                    my $datatype = defined $ref->{ data_type } ? lc( $ref->{ data_type } ) : undef;
                    
                    unless ( $self->_disable_datatype_validation ) {
                        # remember undefined validator type for later ...
                        #   WHY ? because at add_columns-time we dont now about the storage
                        #   engine if using the DBIC::Schema::Loader
                        $self->_unset_validation_datatype->{ $datatype } ||= {};
                        $self->_unset_validation_datatype->{ $datatype }->{ $col } = $ref;
                    }
                    
                    # no specific validation  ..
                    if ( ! defined $self->_validation_by_column->{ $col } ) {
                        $self->_validation_by_column->{ $col } = { validation => { required => 0 } };
                    }
                }
                
                # having validation -> set now
                if ( defined $ref->{ validation } ) {
                    $self->_validation_by_column->{ $col } = delete $ref->{ validation };
                }
                
                # remember the dfault value as well
                $self->_validation_by_column->{ $col }->{ default_value }
                    = $ref->{ default_value }
                    if defined $ref->{ default_value };
                
                $self->_validation_by_column->{ $col }->{ deflate_before } = 1
                    if $ref->{ deflate_before_validation };
                
                $i++;
            }
            # otherwise -> regular optional
            elsif ( ! defined $self->_validation_by_column->{ $col } ) {
                $self->_validation_by_column->{ $col } = { validation => { required => 0 } };
            }
            
        }
        # otherwise -> regular optional
        elsif ( ! defined $self->_validation_by_column->{ $col } ) {
            $self->_validation_by_column->{ $col } = { validation => { required => 0 } };
        }
    }
    
    return $self->next::method( @add_columns );
}


=head2 add_validation_methods

add named validation methods .. should be used in your Model and NOT at runtime .. but of course this is only a convention.. However, you can easily inherit those from your super DBIx::Class-Model

*CAUTION*
If you use it for overwriting at runtime remember that you (probably) are
in a mutli thread environment. The NEXT update or create process will use 
this. Wheter it is the object you hope it would or another, happen to
update / create at the same time!

=cut

sub add_validation_methods {
    my ( $self, $methods_ref ) = @_;
    
    $self->_validation_methods( {} ) unless $self->_validation_methods;
    $self->_validation_methods->{ $_ } = $methods_ref->{ $_ }
        for keys %$methods_ref;
    
    return $self;
}


=head2 add_validated_column_class

Pre-Setting named validated columns for easyier re-use

    package Schema::MySuper;
    use base qw/ DBIx::Class /;
    # ..
    __PACKAGE__->add_validated_column_class( {
        integer => {
            default_value => 0,
            validation => {
                constraint => qr~^\d+$~
            }
        }
    } );
    
    package Schema::MyOther;
    use base qw/ Schema::MySuper /M
    # ...
    __PACKAGE__->add_validated_columns(
        some_column => 'class:integer'
    );

=cut

sub add_validated_column_class {
    my ( $self, $classes_ref ) = @_;
    
    $self->_validation_column_class( {} ) unless $self->_validation_column_class;
    $self->_validation_column_class->{ $_ } = $classes_ref->{ $_ }
        for keys %$classes_ref;
    
    return $self;
}



=head2 deflate_before_validation

sets wheter the deflate methods shall be called on input BEFORE validation!

=cut

sub deflate_before_validation {
    my ( $self, $enable ) = @_;
    $self->_deflate_before_validation( $enable );
}


=head2 set_runtime_validation_methods

Sets validation methods at runtime. Those will be set exclusivly to this one current object!!

=cut

sub set_runtime_validation_methods {
    my ( $self, $methods_ref ) = @_;
    __PACKAGE__->_runtime_validation_methods( $methods_ref );
    return $self;
}


=head2 clear_runtime_validation_methods

Clears all runtime methods .. same as calling set without params

=cut

sub clear_runtime_validation_methods {
    my ( $self ) = @_;
    __PACKAGE__->_runtime_validation_methods( {} );
    return $self;
}




=head2 validates

Validates data for the rules of the object.

    my $res = $object->validates( $data_ref, qw/ validator1 validator2 / );
    if( $res->error ) {
        print "Nope, invalid:\n".
            "\tMissing: ". join( ", ", $res->missing() ). "\n".
            "\tInvalid: ". join( ", ", $res->invalid() ). "\n"
        ;
    }
    else {
        print "Yes, valid, filtered data is: ". Data::Dumper::Dumper( $res->data ). "\n";
    }

Example for better understanding the work:

    # the following ..
    my $res = $object->validates( $data_ref, qw/ default update / )
    if ( ! $res->error ) {
        $object->update( $res->data );
    }
    
    # .. is nearly the same as
    $object->update_validated( $date_ref );

The primary difference is that the update_validated call will throw an exception if 
validation fails where the validates-method simply returns a result object, which 
includes errors on failure (but doesnt die!)

=cut

sub validates {
    my ( $self, $data_ref, @profile_names ) = @_;
    
    # validate and try retreive data
    my ( $filtered_ref, $e ) = $self->_validate_all( $data_ref, { die_ok => 0 }, @profile_names );
    
    # another error occured, which is not an DBIx::Class::DataValidator::*Exception!
    $e = DBIx::Class::DataValidator::Exception->new( $e )
        if $e && ( ! blessed( $e ) || ! $e->isa( 'DBIx::Class::DataValidator::Exception' ) );
    
    return DBIx::Class::DataValidator::ValidationResult->new( $filtered_ref || $self->{ _clear_data }, $e );
}




=head2 validates_for_update

Same as validates but used for existing objects. It does merge in all (not given) data.
This is mandatory if you dont want the existing data been overwritten by provided 
default-data. Imagine you perform a partilal update.

    my $object => $schema->resultset( 'Foo' )->find( $id );
    if ( $object->validates_for_update( $partial_data_ref, qw/ profile1 profile2 / ) ) {
        $object->update( $partial_data_ref );
    }

=cut

sub validates_for_update {
    my ( $self, $data_ref, @profile_names ) = @_;
    my $merged_data_ref = $self->_merge_for_update( $data_ref );
    return $self->validates( $merged_data_ref, @profile_names );
}



=head2 update_validated

performs the update method, but runs validate before. If validation fails it will die 
(with a DBIx::Class::DataValidator::UpdateException-Exception).

    # uses "default" and / or "update" validator
    $object->update_validated( $update_data_ref );
    
    # uses only given validators
    $object->update_validated( $update_data_ref, @validators );

Before the actual update all existing data will be merged with the update data. This way you
can (and have to) always assume that any column is present in your validators. 

=cut

sub update_validated {
    my ( $self, $data_ref, @validators ) = @_;
    unless( @validators ) {
        push @validators, 'default' if defined $self->_validation_profiles->{ default };
        push @validators, 'update' if defined $self->_validation_profiles->{ update };
    }
    my $save_ref;
    eval {
        $save_ref = $self->_validate_all(
            $self->_merge_for_update( $data_ref ), { die_ok => 1 }, @validators );
    };
    
    if ( my $e = $@ ) {
        die DBIx::Class::DataValidator::UpdateException->new( $e ) unless blessed( $e );
        die bless $e, "DBIx::Class::DataValidator::UpdateException";
    }
    
    my $object;
    eval {
        $object = $self->update_not_validated( $save_ref );
    };
    if ( my $e = $@ ) {
        die DBIx::Class::DataValidator::CreateException->new( $e );
    }
    
    return $object;
}


=head2 update_not_validated

Always call the non validating update method (aka regular update methdo from L<DBIx::Class>)
For internal usage and if you have not explicitly disabled native validation with
disable_native_validation

=cut

sub update_not_validated {
    my ( $self, $data_ref ) = @_;
    $self->_disable_native_validation_temp( 1 );
    my $object;
    eval { # catch update error here and assure temp disabling will be switched off afterwards!
        $object = $self->update( $data_ref );
    };
    my $err = $@;
    $self->_disable_native_validation_temp( 0 );
    croak $err if $err;
    return $object;
}


=head2 update

modified update. If you have not disabled native validation it simply calls the regular
update method. If native validation is not disabled, it will call the update_validated method
instead.

    # in your model
    __PACKAGE__->disable_native_validation(0);
    
    # somewhere else
    $object->update( $data_ref, qw/ validator1 validator2 / );
    
    # would be the same as
    $object->update_validated( $data_ref, qw/ validator1 validator2 / );
    
    # call direct update method withouth validation anyway
    $object->update_not_validated( $data_ref );

=cut

sub update {
    my ( $self, $data_ref, @validators ) = @_;
    if ( $self->_disable_native_validation_temp || $self->disable_native_validation ) {
        return $self->next::method( $data_ref );
    }
    else {
        return $self->update_validated( $data_ref, @validators );
    }
}


=head2 disable_datatype_validation

disables data type specific validation for the DBMS, has to be used in the model

    package MySchema::MyModel;
    
    # ..
    __PACKAGE__->disable_datatype_validation( 1 );

=cut

sub disable_datatype_validation {
    my ( $self, $disable ) = @_;
    $self->_disable_datatype_validation( $disable );
}




=head1 PRIVATE METHODS



=head2 _validate_all

validates multiple profiles and throws Exception if not valid 

    eval {
        $obj->_validate_all( $data_ref, $profile1_name, $profile2_name );
    };
    if ( my $e = $@ ) {
        warn "Errors occured: \n".
            "Message : ". $e->message. "\n".
            "Invalids: ". join( ", ", $e->invalid ). "\n".
            "Missing : ". join( ", ", $e->missing ). "\n"
        ;
    }

=cut

sub _validate_all {
    my ( $self, $data_ref, $args_ref, @profile_names ) = @_;
    $args_ref ||= { die_ok => 1 };
    
    # late init of the dbms specific validation because we have no information
    #   about the dbms with DBIC::Schema::Loader at add_columns-time
    if ( ! $self->_validation_dbms_inited && $self->_unset_validation_datatype ) {
        $self->_validation_dbms_inited( 1 );
        
        # determine the storage class
        my $result_source = ref( $self ) && $self->isa( 'DBIx::Class' )
            ? $self->result_source
            : $self->result_source_instance
        ;
        ( my $storage_class = ref( $result_source->storage ) )
            =~ s/^DBIx::Class::Storage::DBI::(.+?)$/$1/;
        
        # try to load the storage DBSM datatype validation class
        eval "__PACKAGE__->load_components( qw/ Component::DataValidator::DBMS::$storage_class / )";
        
        # DBMS could be loaded ..
        unless ( $@ ) {
            
            # go through all datatypes ..
            while ( my ( $datatype, $cols_ref ) = each %{ $self->_unset_validation_datatype } ) {
                next unless $self->can_datatype_validator( $datatype );
                
                # determine validation for each column
                while ( my ( $col, $ref ) = each %{ $cols_ref } ) {
                    
                    # check wheter there is already a validation registered for this column
                    my %validation = %{ $self->_validation_by_column->{ $col } || {} };
                    next if scalar keys %validation > 1 || $validation{ required };
                    
                    # load the validation and link to column
                    $ref = $self->load_datatype_validator( $datatype, $ref, $col );
                    $self->_validation_by_column->{ $col } = delete $ref->{ validation }
                        if $ref->{ validation };
                }
            }
        }
        
        # oops, couldn load this storage class!
        else {
            warn "Cannot load DBMS specific validation for $storage_class ($result_source). Searched for DBIx::Class::Component::DataValidator::DBMS::$storage_class but couldnt find .. not installed ?";
        }
    }
    
    # using "default" as ... right: default
    $self->_validation_profiles( {} ) unless $self->_validation_profiles;
    @profile_names = qw/ default /
        if ! @profile_names && defined $self->_validation_profiles->{ default };
    
    # check validators for existance and build merged validator
    my %validator = ();
    foreach my $profile( @profile_names ) {
        die DBIx::Class::DataValidator::Exception->new( "No such profile '$profile'. Use '__PACKAGE__->add_validation_profiles( { \"$profile\" => { .. } )' to add it!" )
            unless defined $self->_validation_profiles->{ $profile };
        $validator{ $profile } = { %{ $self->_validation_profiles->{ $profile } } }; # simple clone
    }
    
    # generate the validator from the columns (if so)
    my $any_column_validation = 0;
    my %column_validator = (
        required => [],
        optional => [],
        filters => [ qw/ trim / ],
        constraint_methods => {},
        defaults => {}
    );
    while( my ( $column, $ref ) = each %{ $self->_validation_by_column } ) {
        
        $any_column_validation ++;
        if ( $ref->{ required } ) {
            push @{ $column_validator{ required } }, $column;
        }
        else {
            push @{ $column_validator{ optional } }, $column;
        }
        $column_validator{ constraint_methods }->{ $column } = $ref->{ constraint }
            if defined $ref->{ constraint };
        
        $column_validator{ defaults }->{ $column } = $ref->{ default_value }
            if defined $ref->{ default_value };
    }
    
    # having made up a column validtator -> add now
    if ( $any_column_validation ) {
        $validator{ __by_column } = \%column_validator;
        unshift @profile_names, '__by_column';
    }
    
    # check wheter there are validator profiles:
    die DBIx::Class::DataValidator::Exception->new( "No validator defined at all! use '__PACKAGE__->add_validation_profiles( .. )' to add some!" )
        unless @profile_names;
    
    
    # perform validation
    my ( @invalids, @missing ) = ();
    my $filtered_data_ref = {};
    my $df = $self->_data_form_validator;
    $df = $self->update_validator( $df )
        if $self->can( 'update_validator' );
    
    # deflate data before validating it!
    my $deflate_all = $self->_deflate_before_validation;
    my $result_class = ref( $self ) =~ /HASH/ ? $self->result_class : $self;
    
    
    # determine named methods ..
    my %named_methods = ();
    %named_methods = %{ $self->_validation_methods } if $self->_validation_methods;
    
    # having runtime validation methods from resultset or object ? merge them in ..
    %named_methods = ( %named_methods, %{ $result_class->_runtime_validation_methods } )
        if $result_class->_runtime_validation_methods;
    %named_methods = ( %named_methods, %{ $self->_runtime_validation_methods } )
        if $self->_runtime_validation_methods;
    
    foreach my $k( keys %{ $data_ref } ) {
        
        # only for input data which represents an actual column:
        next if ! $k || ! $result_class->has_column( $k );
        
        # only if all shall be deflated OR this collumn shall be deflated!
        next if ! $deflate_all && ! ( defined $self->_validation_by_column->{ $k } && $self->_validation_by_column->{ $k }->{ deflate_before } );
        
        # get info 
        my $col_info_ref = $result_class->column_info( $k );
        
        # if having an actual deflate method -> do deflate
        if ( defined $col_info_ref && defined $col_info_ref->{ _inflate_info } && defined( my $sub_deflate = $col_info_ref->{ _inflate_info }->{ deflate } ) ) {
            my $old = $data_ref->{ $k };
            $data_ref->{ $k } = $sub_deflate->( $old );
        }
    }
    
    
    # collection validation profiles and check named methods!
    foreach my $profile( @profile_names ) {
        my $validator_ref = $validator{ $profile };
        
        # parse the constraints and replace all named methods!
        my %original_constraints = ();
        if ( defined( my $constraints_ref = $validator_ref->{ constraint_methods } ) ) {
            
            # parse any constraint
            while ( my ( $name, $ref ) = each %{ $constraints_ref } ) {
                if ( ! ref( $ref ) && $ref =~ /^named:(.+?)$/ ) {
                    my $method = $1;
                    
                    # found the validation
                    if ( defined $named_methods{ $method } ) {
                        $original_constraints{ $name } = $ref;
                        $constraints_ref->{ $name } = $named_methods{ $method };
                    }
                    
                    # not found -> die (runtime error)
                    else {
                        die DBIx::Class::DataValidator::Exception->new( "No such named method '$method' in constraint for column '$name' in profile '$profile'" );
                    }
                }
            }
        }
        
        
        # check now:
        my $results = $df->check( $data_ref, $validator_ref );
        
        # test for errors
        my $err = 0;
        if ( $results->has_invalid ) {
            push @invalids, ( $results->invalid );
            $err++;
        }
        if ( $results->has_missing ) {
            push @missing, ( $results->missing );
            $err++;
        }
        
        # not having any error -> retreive filtered data and merge into result
        unless ( $err ) {
            my $add_filtered_ref = $results->get_filtered_data();
            foreach my $k( keys %{ $add_filtered_ref } ) {
                $filtered_data_ref->{ $k } = $add_filtered_ref->{ $k };
            }
            #$filtered_data_ref = merge( $add_filtered_ref, $filtered_data_ref );
        }
        else {
            warn Dumper( [ "Having error on", {
                data    => $data_ref,
                results => $results
            } ] ) if $ENV{ DUMP_VALIDATION_ERRORS };
        }
        
        
        # set back the temporary overwritten constraints
        if ( defined( my $constraints_ref = $validator_ref->{ constraint_methods } ) ) {
            while ( my ( $name, $ref ) = each %{ $constraints_ref } ) {
                
                # perform set back ..
                $constraints_ref->{ $name } = $original_constraints{ $name }
                    if defined $original_constraints{ $name };
            }
        }
    }
    
    # clear the filterred data .. because it will be directly used !!
    my $cleared_ref = $self->_clear_filtered_for_save( $filtered_data_ref );
    
    my %invalid_unique = (); @invalid_unique{ @invalids } = ();
    my %missing_unique = (); @missing_unique{ @missing } = ();
    
    # die with Exception if any missing or invalids
    
    if ( @invalids || @missing ) {
        my $e = DBIx::Class::DataValidator::Exception->new( {
            invalid => [ keys %invalid_unique ],
            missing => [ keys %missing_unique ]
        } );
        die $e if $args_ref->{ die_ok };
        return wantarray ? ( $cleared_ref, $e ) : $cleared_ref;
    }
    else {
        return wantarray ? ( $cleared_ref ) : $cleared_ref;
    }
}


=head2 _clear_filtered_for_save

Clears data ref by removing all keys which does not exist at columns. In the
validator there are probably

=cut

sub _clear_filtered_for_save {
    my ( $self, $filtered_ref ) = @_;
    
    # use cloned data .. dont touch oringal
    my $cleared_ref = clone( $filtered_ref );
    
    # determine result source (coming from create_valited we already using
    #   a DBIx::Class::ResultSource!)
    my $result_source = ( ! ref( $self ) || ! blessed( $self ) || $self->isa( 'DBIx::Class::ResultSource' ) )
        ? $self
        : $self->result_source
    ;
    
    # remove all obsolete columns from the write data
    foreach my $k( keys %{ $filtered_ref } ) {
        delete $cleared_ref->{ $k } if ! $result_source->has_column( $k );
    }
    
    return $cleared_ref;
}


=head2 _merge_for_update

On update, all not provided data will be merged in. So your validators can
only be written as always all data is provided.

=cut

sub _merge_for_update {
    my ( $self, $data_ref ) = @_;
    
    # determine all columns from self
    my $self_ref = { map {
        my $d = $self->can( $_ ) ? $self->$_ : $self->get_column( $_ );
        ( $_ => $d );
    } $self->result_source->columns };
    
    # merge in all existing columns to have a most complete
    return merge( $data_ref, $self_ref ); # left more important .. left is new!
}


=head1 SEE ALSO

L<Data::DBIx::Class>
L<Data::DBIx::Class::Results>
L<Data::DBIx::Class::Schema>
L<Data::FormValidator>
L<Data::FormValidator::Results>

=head1 AUTHOR

Ulrich Kautz - uk@fortrabbit.de

=cut


1;

#
# Those folooing packages are helper packages!
#   They are used only here and shall not be called outside!
#   No need to make them visible at all ..
#



package DBIx::Class::DataValidator::Exception;
use strict;
use warnings;
use Hash::Merge qw/ merge /;
use Carp qw/ longmess /;

sub new {
    my ( $class, $ref ) = @_;
    
    if ( ref( $ref ) eq 'HASH' ) {
        return bless merge( $ref, { invalid => [], missing => [], message => "" } ), $class;
    }
    
    return bless {
        message => $ref,
        debug_message => longmess( $ref ),
        invalid => [],
        missing => [],
    }, $class;
}

sub message {
    return shift->{ message };
}

sub debug_message {
    return shift->{ message };
}

sub invalid {
    my @invalid = @{ shift->{ invalid } ||= [] };
    return wantarray ? @invalid : scalar @invalid > 0; 
}

sub missing {
    my @missing = @{ shift->{ missing } ||= [] };
    return wantarray ? @missing : scalar @missing > 0; 
}

sub invalid_and_missing {
    return ( $_[0]->missing, $_[0]->invalid );
}

sub has_error {
    my ( $self, $name ) = @_;
    my %fields = (); @fields{ $self->invalid, $self->missing } = ();
    return exists $fields{ $name };
}

1;


package DBIx::Class::DataValidator::CreateException;
use strict;
use warnings;
use base qw/ DBIx::Class::DataValidator::Exception /;
1;

package DBIx::Class::DataValidator::UpdateException;
use strict;
use warnings;
use base qw/ DBIx::Class::DataValidator::Exception /;
1;


package DBIx::Class::DataValidator::ValidationResult;
use strict;
use warnings;

sub new {
    my ( $class, $filtered_ref, $error ) = @_;
    return bless {
        error => $error,
        data  => $filtered_ref,
    }, $class;
}

sub error {
    my $self = shift;
    return $self->{ error };
}
sub error_message {
    my $self = shift;
    return $self->error->message if $self->error;
}
sub invalid {
    my $self = shift;
    return $self->error->invalid if $self->error;
    return;
}
sub missing {
    my $self = shift;
    return $self->error->missing if $self->error;
    return;
}
sub invalid_and_missing {
    my $self = shift;
    return $self->error->invalid_and_missing if $self->error;
    return;
}


sub is_missing {
    my ( $self, $name ) = @_;
    my %missing = (); @missing{ $self->missing } = ();
    return exists $missing{ $name };
}
sub is_invalid {
    my ( $self, $name ) = @_;
    my %invalid = (); @invalid{ $self->invalid } = ();
    return exists $invalid{ $name };
}
sub is_invalid_or_missing {
    my ( $self, $name ) = @_;
    return $self->is_missing( $name ) || $self->is_invalid( $name );
}

sub data {
    my $self = shift;
    return $self->{ data };
}

1;




